package com.kingyea.mobilepolice.apimanage.accesscontrol.handle;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.kingyea.mobilepolice.apimanage.sysvo.bean.RequestObj;
import com.kingyea.mobilepolice.system.Result;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author chenjw
 * @version v1.0.0
 * @description
 * @date Created in 2018/4/3 15:56
 */
@Service
public class ControllerHandleImpl implements ControllerHandle {
    private static final Logger LOG = LoggerFactory.getLogger(ControllerHandleImpl.class);
    private final static FastDateFormat df = FastDateFormat.getInstance("ss-mm-HH-dd-MM-yyyy");// 设置日期格式
    static Cache<String, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
            .maximumSize(1000).build();
    private final List<FunctionName> functionNames = Lists.newArrayList();
    @Autowired
    private DetailHadle impl;

    @Override
    public ObjectData usingAccessControl(Map<String, Object> param) {
        //参数转换
        RequestObj reqObj = JSON.parseObject(JSON.toJSONString(param), RequestObj.class);

        ObjectData objData = new ObjectData();

        final List<FunctionName> functionNames = this.impl.getFunctionName(reqObj.getServiceId());
        if (functionNames.size() == 0) {
            objData.setSuccess(true);
            objData.setMessage("switch_button为0，不做限制！");
            return objData;
        }
        for (int i = 0; i < functionNames.size(); i++) {
            switch (functionNames.get(i)) {
                case AUTH:

                    break;
                case TIME:
                    final long start = System.currentTimeMillis();
                    objData = this.timePeriodControl(reqObj);

                    break;
                case IP:
                    final long start2 = System.currentTimeMillis();
                    objData = this.iPbAndwListControl(param);

                    break;
                case FREQUENCY:
                    final long start3 = System.currentTimeMillis();
                    objData = this.requestFrequencyControl(reqObj);

                    break;
                case FILESIZE:
                    final long start5 = System.currentTimeMillis();
                    objData = this.requestFileSizeControlForExmode(param);

                    break;
                case PARAMFILTER:
                    final long start6 = System.currentTimeMillis();
                    objData = this.requestParamCheck(reqObj);

                    break;
                default:
                    objData.setSuccess(true);
                    break;
            }
            if (!objData.isSuccess()) {
                break;
            }
        }
        return objData;
    }

    /**
     * 关键字过滤
     *
     * @param obj
     * @return
     */
    @Override
    public Object keywordFilter(Object obj, Integer serviceId) {

        List<String> keyword = (List<String>) this.impl.getKeyword(serviceId);

        Object object = dofilter(obj, keyword);
        return object;

    }

    @Override
    public ObjectData dataSizeCheck(Object result, Integer serviceId) {
        ObjectData od = new ObjectData();

        try {

            String filesizeStr = this.impl.getFilesMaxSize(serviceId);
            if (Strings.isNullOrEmpty(filesizeStr)) {
                od.setSuccess(true);
                od.setMessage("数据量大小没有设置，校验通过！");
                return od;
            }
            Integer filesize = Integer.parseInt(filesizeStr);
            Result r = JSON.parseObject(JSON.toJSONString(result), Result.class);
            String str = JSON.toJSONString(r.getData());
            Integer strByteLength = str.getBytes("UTF-8").length;
            if (filesize < strByteLength) {
                od.setSuccess(false);
                od.setMessage("数据量大小超过限制");
            } else {
                od.setSuccess(true);
                od.setMessage("数据量大小符合限制！");
            }
        } catch (Exception e) {
            od.setSuccess(false);
            od.setMessage("数据量大小校验失败" + e.getMessage());
        }
        return od;
    }

    @Override
    public Object keywordFilter2(Object result, Integer serviceId) {
        String s = JSON.toJSONString(result);
        List<String> keyword = (List<String>) this.impl.getKeyword(serviceId);
        Result r = null;
        if (keyword == null) {
            return result;
        }
        for (String str : keyword) {
            s = s.replace(str, "*");
        }
        try {

            r = JSON.parseObject(s, Result.class);
            Result rs = JSON.parseObject(JSON.toJSONString(r.getData()), Result.class);
            r.setData(rs);
        } catch (Exception e) {
            LOG.error(e.getMessage());
        }
        return r;
    }

    private Object dofilter(Object obj, List<String> keyword) {
        String s = JSON.toJSONString(obj);
        // 尝试map
        List l = null;
        Map m = null;
        try {
            m = JSON.parseObject(s, Map.class);
        } catch (Throwable e) {
            //尝试list
            try {
                l = JSON.parseObject(s, List.class);
            } catch (Throwable e1) {
                return null;
            }
        }
        if (m != null) {
            for (Object mapKey : m.keySet()) {
                String tmp = m.get(mapKey).toString();
                for (String word : keyword) {

                    tmp = tmp.replace(word, "*");
                }
                m.put(mapKey, tmp);
                dofilter(m.get(mapKey), keyword);
            }
            return m;
        } else {
            // TODO: 遍历回调
            List res = new ArrayList();
            for (int i = 0, len = l.size(); i < len; i++) {
                Object o = dofilter(l.get(i), keyword);
                res.add(o);
            }

            return res;
        }
    }

    /**
     * 参数校验
     * TODO
     *
     * @param reqObj
     * @return
     */
    private ObjectData requestParamCheck(RequestObj reqObj) {
        final ObjectData objectData = new ObjectData();
        Integer serviceId = reqObj.getServiceId();
        final List<Map> ruleMap = this.impl.getParametricRule(serviceId);
        Map<String, Object> params = reqObj.getParams();
        if (ruleMap.size() == 0) {
            objectData.setSuccess(true);
            objectData.setMessage("验证通过（无限制）！");
            return objectData;
        }
        boolean flag = false;
        for (Map m : ruleMap) {
            String paramName = m.get("name").toString();
            String rule = m.get("value").toString();
            String param = params.get(paramName).toString();
            flag = docheck(param, rule);
            if (!flag) {
                objectData.setSuccess(false);
                objectData.setMessage("参数" + param + "不符合校验规则：" + rule);
            }
        }

        return objectData;
    }

    private boolean docheck(String param, String rule) {
        final Pattern pat = Pattern.compile(rule);
        final Matcher mat = pat.matcher(param);
        final boolean result = mat.find();
        return result;
    }

    /**
     * 数据量限制
     * TODO 2018-04-04 暂无思路
     *
     * @param param
     * @return
     */
    private ObjectData requestFileSizeControlForExmode(Map<String, Object> param) {
        final ObjectData objectData = new ObjectData();
        Integer dataSize = (Integer) param.get("dataSize");
        RequestObj reqObj = JSON.parseObject(JSON.toJSONString(param), RequestObj.class);
        final Integer serviceID = reqObj.getServiceId();
        final String map = this.impl.getFilesMaxSize(serviceID);

        // TODO 2018-4-4 如何判断数据量不明确
//        if (map.get(serviceID) != null) {
//            final String sizeStr = map.get(serviceID).toString();
//
//            if (org.apache.commons.lang3.StringUtils.isNotBlank(sizeStr)) {
//                final DataExchangeServiceDescript descritp = message.getServiceDescript();
//                final List<Attachment> attList = descritp.getFiles();
//                final Long sizeLimit = Long.parseLong(sizeStr);
//
//                for (int i = 0; i < attList.size(); i++) {
//                    final Attachment attachment = attList.get(i);
//                    if (attachment.getSize() > sizeLimit) {
//
//                        objectData.setSuccess(false);
//                        objectData.setCode(ICorssInterfaceCallFailedErrCode.OTHER_ERR.code);
//                        objectData.setMessage("验证失败,文件:" + attachment.getName() + ",超过最大限制:" + sizeLimit);
//                        return objectData;
//                    }
//                }
//
//            }
//
//        }

        objectData.setSuccess(true);
        objectData.setMessage("验证通过");
        return objectData;
    }

    /**
     * 请求频率控制
     *
     * @param reqObj
     * @return
     */
    private ObjectData requestFrequencyControl(RequestObj reqObj) {
        ObjectData objectDataSpec = null;
        final ObjectData objectDataWhole = this.requestFrequency(reqObj, "whole");// 服务方全局的时间段
        if (!objectDataWhole.isSuccess()) {
            return objectDataWhole;
        } else {
            objectDataSpec = this.requestFrequency(reqObj, "special");// 单个请求方和服务方的时间段
        }
        return objectDataSpec;
    }

    private ObjectData requestFrequency(RequestObj reqObj, String type) {
        final ObjectData objectData = new ObjectData();
        final Integer senderID = reqObj.getRequestId();
        final Integer serviceID = reqObj.getServiceId();
        InvocationCount invo = null;
        try {
            final String requestFrequency = this.impl.getRequestFrequency(senderID, serviceID, type);

            if (StringUtils.isBlank(requestFrequency)) {
                LOG.info("访问频率设置为空，即请求需持续可用，验证通过");
                objectData.setSuccess(true);
                objectData.setMessage("验证通过");
                return objectData;
            }

            final String[] requestFrequencyArr = requestFrequency.split(",");
            invo = (InvocationCount) cache.get(senderID + "_" + serviceID + "_" + type, () -> {
                // 初始化请求频率
                final InvocationCount invoCation = new InvocationCount();
                if (requestFrequencyArr.length == 1) {
                    final String frequencyStr = requestFrequencyArr[0];
                    final Long frequency = Long.parseLong(frequencyStr.substring(1, frequencyStr.length()));
                    if (frequencyStr.startsWith("H")) {
                        invoCation.setHourCount(frequency);
                    } else if (frequencyStr.startsWith("D")) {
                        invoCation.setDailyCount(frequency);
                    } else if (frequencyStr.startsWith("W")) {
                        invoCation.setWeekCount(frequency);
                    } else if (frequencyStr.startsWith("M")) {
                        invoCation.setMonthCount(frequency);
                    }
                } else {
                    invoCation.setHourCount(
                            Long.parseLong(requestFrequencyArr[0].substring(1, requestFrequencyArr[0].length())));
                    invoCation.setDailyCount(
                            Long.parseLong(requestFrequencyArr[1].substring(1, requestFrequencyArr[1].length())));
                    invoCation.setWeekCount(
                            Long.parseLong(requestFrequencyArr[2].substring(1, requestFrequencyArr[2].length())));
                    invoCation.setMonthCount(
                            Long.parseLong(requestFrequencyArr[3].substring(1, requestFrequencyArr[3].length())));
                }
                invoCation.init(senderID + "_" + serviceID + "_" + type);
                return invoCation;
            });
        } catch (final ExecutionException e) {
            LOG.error("获取计数器失败", e);
        }
        if (invo.forbidden()) {
            objectData.setSuccess(false);
            objectData.setMessage("验证失败," + invo.resultEntries().get(Keys.MESSAGE));
            return objectData;
        } else {
            if (!invo.checkInited()) {
                invo.init(senderID + "_" + serviceID + "_" + type);
            }
            invo.heartBeat();
            objectData.setSuccess(true);
            objectData.setMessage("验证通过");
        }
        return objectData;
    }

    /**
     * ip黑白名单校验
     *
     * @param map
     * @return
     */
    private ObjectData iPbAndwListControl(Map<String, Object> map) {
        final ObjectData objectData = new ObjectData();
        final String requestIP = map.get("reqIp").toString();
        RequestObj reqObj = JSON.parseObject(JSON.toJSONString(map), RequestObj.class);

        final Integer senderID = reqObj.getRequestId();
        final Integer serviceID = reqObj.getServiceId();

        final boolean flag = this.impl.getIpAuthen(senderID, serviceID, requestIP);
        if (flag) {
            objectData.setSuccess(true);
            objectData.setMessage("ip验证通过");
        } else {
            objectData.setSuccess(false);
            objectData.setMessage("验证失败，请求ip在黑名单里面！");
        }

        return objectData;

    }

    /**
     * 时间段限制
     *
     * @param reqObj
     * @return
     */
    private ObjectData timePeriodControl(RequestObj reqObj) {

        ObjectData objectDataSpec = null;
        objectDataSpec = this.timePeriod(reqObj, "whole");// 服务方全局的时间段
        if (!objectDataSpec.isSuccess()) {
            return objectDataSpec;
        } else {
            objectDataSpec = this.timePeriod(reqObj, "special");// 单个请求方和服务方的时间段
        }
        return objectDataSpec;

    }

    private ObjectData timePeriod(RequestObj reqObj, String type) {
        ObjectData objectData = new ObjectData();
        final Integer senderID = reqObj.getRequestId();
        final Integer serviceID = reqObj.getServiceId();
        // 获取：获取服务授权申请信息表记录的允许访问时间段
        final String cron = this.impl.getAccredit(senderID, serviceID, type);
        // 解析时间段cron表达式
        if (StringUtils.isBlank(cron)) {
            objectData.setSuccess(true);
            objectData.setMessage("时间段验证通过！");
        } else {
            final String[] cronArr = cron.split(" ");

            final String requestTime = df.format(new Date());
            final String[] requestTimeArr = requestTime.split("-");
            boolean flag = true;
            for (int i = 0; i < cronArr.length; i++) {// 循环cron表达式，对每个时限进行比较
                if (i == 5) {// 对周进行比较
                    if (cronArr[i].equals("*") || cronArr[i].equals("0") || cronArr[i].equals("?")) {// 不限制
                        flag = true;
                        continue;
                    }
                    final int w = this.getWeek();// 获取当前日期是星期几
                    if (cronArr[i].contains(",")) {
                        final String[] _cronArr = cronArr[i].split(",");
                        flag = Arrays.binarySearch(_cronArr, w) > 0 ? true : false;
                    } else if (cronArr[i].contains("/")) {// 当表达式有两个时限区间的时候比较,只要在其中一个区间里面就符合

                        final String[] cronArrWeek = cronArr[i].split("/");
                        int cronArrTure = 0;
                        for (int j = 0; j < cronArrWeek.length; j++) {
                            final String[] _cronArr = cronArrWeek[j].split("-");
                            if ((Integer.parseInt(requestTime) < Integer.parseInt(_cronArr[0]))
                                    || (Integer.parseInt(requestTime) > Integer.parseInt(_cronArr[1]))) {
                            } else {
                                cronArrTure++;
                            }
                        }
                        if (cronArrTure == 0) {
                            flag = false;
                        }

                    } else {
                        if (!String.valueOf(w).equals(cronArr[i])) {
                            flag = false;
                        }
                    }
                } else {
                    if (i == 6) {
                        flag = this.proofTime(cronArr[i], requestTimeArr[i - 1]);
                    } else {
                        flag = this.proofTime(cronArr[i], requestTimeArr[i]);
                    }
                }
                if (flag == false) {
                    objectData.setSuccess(false);
                    objectData.setMessage("验证失败，不在业务请求时间段内！");
                    break;
                }
            }
            if (flag) {
                objectData.setSuccess(true);
                objectData.setMessage("验证通过！");
            }
        }

        return objectData;
    }

    /**
     * 验证当前时间区间和配置的时间区间是否适配
     * 单个字符格式可能有 0 * ?
     * 多个字符格式可能有 0/2 0-2 0,1 0-3/6-12 0-3,6-12
     *
     * @return
     */
    protected boolean proofTime(final String cronTime, final String requestTime) {
        if (cronTime.equals("0") || cronTime.equals("*") || cronTime.equals("?")) {// 不限时间
            return true;
        }
        if (cronTime.contains("/")) {// 同一个时限，有多个区间
            final String[] cronArr = cronTime.split("/");
            int cronArrTure = 0;
            for (int i = 0; i < cronArr.length; i++) {
                final String[] _cronArr = cronArr[i].split("-");
                if ((Integer.parseInt(requestTime) < Integer.parseInt(_cronArr[0]))
                        || (Integer.parseInt(requestTime) > Integer.parseInt(_cronArr[1]))) {
                } else {
                    cronArrTure++;
                }
            }
            if (cronArrTure == 0) {
                return false;
            }
        } else if (cronTime.contains("-")) {// 同一个时限，只有一个区间
            final String[] cronArr = cronTime.split("-");
            if ((Integer.parseInt(requestTime) < Integer.parseInt(cronArr[0]))
                    || (Integer.parseInt(requestTime) > Integer.parseInt(cronArr[1]))) {
                return false;
            }
        } else if (cronTime.contains(",")) {// 多个时间逗号隔开
            final String[] cronArr = cronTime.split(",");
            return Arrays.binarySearch(cronArr, requestTime) > 0;
        } else {
            if (Integer.parseInt(requestTime) != Integer.parseInt(cronTime)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取当前日期
     *
     * @return
     */
    protected int getWeek() {
        final Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        final int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
        return w;
    }

    public List<FunctionName> getFunctionNames() {
        return functionNames;
    }

    public DetailHadle getImpl() {
        return impl;
    }

    public void setImpl(DetailHadle impl) {
        this.impl = impl;
    }
}
